/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.common;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.List;

/**
 * common层的配置类
 *
 * <ul>
 *     <li>redis配置定义</li>
 * </ul>
 * @author yoara
 */
public class ConfigFactory {
    public static final String COMMON_CACHE_BEAN_NAME = "yhaoCache";

    //redis是否启用
    public static final String BASIC_REDIS_ON = "basic.redis.on";
    //redis连接池最大连接数量
    public static final String BASIC_REDIS_POOL_MAXTOTAL = "basic.redis.pool.maxTotal";
    //redis连接池最大闲置数量
    public static final String BASIC_REDIS_POOL_MAXIDLE = "basic.redis.pool.maxIdle";
    //redis连接池最大闲置时间
    public static final String BASIC_REDIS_POOL_MAXWAITMILLIS = "basic.redis.pool.maxWaitMillis";
    //redis应用名，配置了应用名的数据会流入该命名空间内，否则流进默认的命名空间CLOUD_ANONYMOUS
    public static final String BASIC_REDIS_APP_NAME = "basic.redis.appName";
    //写redis的url配置
    public static final String BASIC_REDIS_SAVE_URL = "basic.redis.save.url";
    //写redis的端口配置，如果是集群设置，则不需要填写这一项
    public static final String BASIC_REDIS_SAVE_PORT = "basic.redis.save.port";
    //写redis的密码
    public static final String BASIC_REDIS_SAVE_PASSWORD = "basic.redis.save.password";
    //读redis的url配置
    public static final String BASIC_REDIS_QUERY_URL = "basic.redis.query.url";
    //读redis的端口配置，如果是集群设置，则不需要填写这一项
    public static final String BASIC_REDIS_QUERY_PORT = "basic.redis.query.port";
    //读redis的密码
    public static final String BASIC_REDIS_QUERY_PASSWORD = "basic.redis.query.password";
    //是否使用集群设置，优先级顺序unable>enable>deploy.env
    public static final String BASIC_REDIS_CLUSTER_ENABLE = "basic.redis.cluster.enable";
    public static final String BASIC_REDIS_CLUSTER_UNABLE = "basic.redis.cluster.unable";
    //日志记录
    public static final String LOGGER_APP = "logger.app";
    private static Logger logger = LoggerFactory.getLogger(ConfigFactory.class);

    public static RedisTemplate makeRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }


    /**
     * Redis可自定义集群加载模式
     */
    public static JedisConnectionFactory makeJedisConnectionFactory(
            boolean useCluster,Environment env,String envUrl, String envPort,
            String envPassword,JedisPoolConfig jedisPoolConfig) {
        RedisConnectInfo connectInfo = new RedisConnectInfo(env,useCluster,
                envUrl,envPort,envPassword);
        return makeJedisConnectionFactory(connectInfo,jedisPoolConfig);
    }

    /**
     * Redis默认使用配置文件中的集群加载模式
     * @return
     */
    public static JedisConnectionFactory makeJedisConnectionFactory(Environment env,String envUrl, String envPort,
            String envPassword,JedisPoolConfig jedisPoolConfig) {
        RedisConnectInfo connectInfo = new RedisConnectInfo(env,isUseCluster(env),
                envUrl,envPort,envPassword);
        return makeJedisConnectionFactory(connectInfo,jedisPoolConfig);
    }

    private static JedisConnectionFactory makeJedisConnectionFactory(
            RedisConnectInfo connectInfo, JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory factory;

        if (connectInfo.useCluster) {
            RedisClusterConfiguration clusterConfig = getClusterConfig(connectInfo.envUrl);
            factory = new JedisConnectionFactory(clusterConfig);
        } else {
            factory = new JedisConnectionFactory();
            factory.setHostName(connectInfo.envUrl);
            factory.setPort(connectInfo.envPort);
        }
        if(StringUtils.isNotEmpty(connectInfo.envPassword)){
            factory.setPassword(connectInfo.envPassword);
        }
        factory.setPoolConfig(jedisPoolConfig);
        return factory;
    }

    /**
     * Lettuce自定义集群加载模式
     * @return
     */
    public static LettuceConnectionFactory makeLettuceConnectionFactory(
            boolean useCluster,Environment env,String envUrl, String envPort,String envPassword) {

        RedisConnectInfo connectInfo = new RedisConnectInfo(env,useCluster,
                envUrl,envPort,envPassword);
        return makeLettuceConnectionFactory(connectInfo);
    }

    /**
     * Lettuce默认使用配置文件中的集群加载模式
     * @return
     */
    public static LettuceConnectionFactory makeLettuceConnectionFactory(
            Environment env,String envUrl, String envPort,String envPassword) {

        RedisConnectInfo connectInfo = new RedisConnectInfo(env,isUseCluster(env),
                envUrl,envPort,envPassword);
        return makeLettuceConnectionFactory(connectInfo);
    }

    private static LettuceConnectionFactory makeLettuceConnectionFactory(RedisConnectInfo connectInfo){
        LettuceConnectionFactory factory;
        if (connectInfo.useCluster) {
            RedisClusterConfiguration clusterConfig = getClusterConfig(connectInfo.envUrl);
            factory = new LettuceConnectionFactory(clusterConfig);
        } else {
            factory = new LettuceConnectionFactory();
            factory.setHostName(connectInfo.envUrl);
            factory.setPort(connectInfo.envPort);
        }
        if(StringUtils.isNotEmpty(connectInfo.envPassword)){
            factory.setPassword(connectInfo.envPassword);
        }
        return factory;
    }

    private static boolean isUseCluster(Environment env) {
        boolean enAbleCluster = StringUtils.isNotEmpty(env.getProperty(BASIC_REDIS_CLUSTER_ENABLE));
        boolean unAbleCluster = StringUtils.isNotEmpty(env.getProperty(BASIC_REDIS_CLUSTER_UNABLE));

        return unAbleCluster?
                false:enAbleCluster?
                true:"pro".equalsIgnoreCase(env.getProperty("deploy.env"))?
                true:false;
    }

    private static RedisClusterConfiguration getClusterConfig(String envUrl) {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
        String[] ips = envUrl.split(",");
        List<RedisNode> list = new ArrayList<>(ips.length * 2);
        for (String s : ips) {
            String[] nodes = s.trim().split(":");
            RedisNode node = new RedisNode(nodes[0].trim(), Integer.parseInt(nodes[1].trim()));
            list.add(node);
        }
        clusterConfig.setClusterNodes(list);
        clusterConfig.setMaxRedirects(ips.length);
        return clusterConfig;
    }

    static class RedisConnectInfo{
        private boolean useCluster;
        private String envUrl;
        private Integer envPort;
        private String envPassword;

        public RedisConnectInfo(Environment env,boolean useCluster,
                                String envUrl, String envPort, String envPassword) {
            this.useCluster = useCluster;
            this.envUrl = env.getProperty(envUrl);
            this.envPort = env.getProperty(envPort, Integer.class);
            this.envPassword = env.getProperty(envPassword);
            logger.info("===init redis[url]:"+this.envUrl+",[port]:"+this.envPort+",[useCluster]:"+useCluster);
        }
    }
}
